#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals, improper_ctypes)]
use core::ffi::c_void;
use core::ops::Deref;
use std::ffi::c_char;

#[cfg(target_pointer_width = "64")]
pub type CGFloat = core::ffi::c_double;
#[cfg(not(target_pointer_width = "64"))]
pub type CGFloat = core::ffi::c_float;
pub type UInt8 = core::ffi::c_uchar;
pub type UInt16 = core::ffi::c_ushort;
pub type SInt16 = core::ffi::c_short;
pub type SInt32 = core::ffi::c_int;
pub type UInt32 = core::ffi::c_uint;
pub type Boolean = u8;
pub type CFIndex = isize;
pub type CFHashCode = usize;

pub type CFRef = *const c_void;
pub type CFAllocatorRef = *const c_void;

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CFRange {
	pub location: CFIndex,
	pub length: CFIndex,
}
impl CFRange {
	pub fn init(location: CFIndex, length: CFIndex) -> CFRange {
		CFRange { location, length }
	}
}

#[repr(C)]
pub struct __CFValue(c_void);
pub type CFValueRef = *const __CFValue;
pub struct CFValue(CFValueRef);
impl CFValue {
	#[inline]
	pub fn get_rule(reference: CFValueRef) -> Self {
		assert!(!reference.is_null());
		unsafe { Self::create_rule(CFRetain(reference as _) as _) }
	}
	#[inline]
	pub fn create_rule(reference: CFValueRef) -> Self {
		assert!(!reference.is_null());
		CFValue(reference)
	}
	#[inline]
	pub fn as_ref(&self) -> CFValueRef {
		self.0
	}
	#[inline]
	pub fn as_cf(&self) -> CFRef {
		self.0 as CFRef
	}
}
impl Drop for CFValue {
	fn drop(&mut self) {
		unsafe { CFRelease(self.as_ref() as _) }
	}
}
impl Clone for CFValue {
	fn clone(&self) -> Self {
		Self::get_rule(self.0)
	}
}

pub type CFNumberRef = CFValueRef;
#[derive(Clone)]
pub struct CFNumber(pub CFValue);
impl Deref for CFNumber {
	type Target = CFValue;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl From<CFNumber> for CFValue {
	fn from(value: CFNumber) -> Self {
		value.0
	}
}
impl From<i32> for CFNumber {
	#[inline]
	fn from(value: i32) -> Self {
		unsafe {
			let number_ref = CFNumberCreate(
				kCFAllocatorDefault, //
				kCFNumberSInt32Type,
				&value as *const i32 as _,
			);
			CFNumber(CFValue::create_rule(number_ref))
		}
	}
}
impl From<i64> for CFNumber {
	#[inline]
	fn from(value: i64) -> Self {
		unsafe {
			let number_ref = CFNumberCreate(
				kCFAllocatorDefault, //
				kCFNumberSInt64Type,
				&value as *const i64 as _,
			);
			CFNumber(CFValue::create_rule(number_ref))
		}
	}
}
impl From<f32> for CFNumber {
	#[inline]
	fn from(value: f32) -> Self {
		unsafe {
			let number_ref = CFNumberCreate(
				kCFAllocatorDefault, //
				kCFNumberFloat32Type,
				&value as *const f32 as _,
			);
			CFNumber(CFValue::create_rule(number_ref))
		}
	}
}
impl From<f64> for CFNumber {
	#[inline]
	fn from(value: f64) -> Self {
		unsafe {
			let number_ref = CFNumberCreate(
				kCFAllocatorDefault, //
				kCFNumberFloat64Type,
				&value as *const f64 as _,
			);
			CFNumber(CFValue::create_rule(number_ref))
		}
	}
}

pub type CFArrayRef = CFValueRef;
#[derive(Clone)]
pub struct CFArray(pub CFValue);
impl Deref for CFArray {
	type Target = CFValue;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl From<CFArray> for CFValue {
	fn from(value: CFArray) -> Self {
		value.0
	}
}
impl CFArray {
	#[inline]
	pub fn len(&self) -> CFIndex {
		unsafe { CFArrayGetCount(self.as_ref()) }
	}
	#[inline]
	pub fn get(&self, index: CFIndex) -> Option<CFRef> {
		if index < self.len() {
			let v = unsafe { CFArrayGetValueAtIndex(self.as_ref(), index) };
			if !v.is_null() {
				Some(v)
			} else {
				None
			}
		} else {
			None
		}
	}
}

pub type CFStringRef = CFValueRef;
#[derive(Clone)]
pub struct CFString(pub CFValue);
impl Deref for CFString {
	type Target = CFValue;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl From<CFString> for CFValue {
	fn from(value: CFString) -> Self {
		value.0
	}
}
impl CFString {
	#[inline]
	pub fn new(string: &str) -> CFString {
		unsafe {
			let string_ref = CFStringCreateWithBytes(
				kCFAllocatorDefault,
				string.as_ptr(),
				string.len() as _,
				kCFStringEncodingUTF8,
				false as Boolean,
			);
			CFString(CFValue::create_rule(string_ref))
		}
	}
	#[inline]
	pub fn from_static_string(string: &'static str) -> CFString {
		unsafe {
			let string_ref = CFStringCreateWithBytesNoCopy(
				kCFAllocatorDefault,
				string.as_ptr(),
				string.len() as _,
				kCFStringEncodingUTF8,
				false as Boolean,
				kCFAllocatorNull,
			);
			CFString(CFValue::create_rule(string_ref))
		}
	}
	#[inline]
	pub fn char_len(&self) -> CFIndex {
		unsafe { CFStringGetLength(self.as_ref()) }
	}
}

mod string {
	use super::*;
	use core::{fmt, str};
	use std::borrow::Cow;
	use std::ffi::CStr;
	use std::ptr;

	impl<'a> From<&'a CFString> for Cow<'a, str> {
		fn from(cf_str: &'a CFString) -> Cow<'a, str> {
			unsafe {
				// Do this without allocating if we can get away with it
				let c_string = CFStringGetCStringPtr(cf_str.as_ref(), kCFStringEncodingUTF8);
				if !c_string.is_null() {
					let c_str = CStr::from_ptr(c_string);
					Cow::Borrowed(str::from_utf8_unchecked(c_str.to_bytes()))
				} else {
					let char_len = cf_str.char_len();

					// First, ask how big the buffer ought to be.
					let mut bytes_required: CFIndex = 0;
					CFStringGetBytes(
						cf_str.as_ref(),
						CFRange {
							location: 0,
							length: char_len,
						},
						kCFStringEncodingUTF8,
						0,
						false as Boolean,
						ptr::null_mut(),
						0,
						&mut bytes_required,
					);

					// Then, allocate the buffer and actually copy.
					let mut buffer = vec![b'\x00'; bytes_required as usize];

					let mut bytes_used: CFIndex = 0;
					let chars_written = CFStringGetBytes(
						cf_str.as_ref(),
						CFRange {
							location: 0,
							length: char_len,
						},
						kCFStringEncodingUTF8,
						0,
						false as Boolean,
						buffer.as_mut_ptr(),
						buffer.len() as _,
						&mut bytes_used,
					);
					assert_eq!(chars_written, char_len);

					// This is dangerous; we over-allocate and null-terminate the string (during
					// initialization).
					assert_eq!(bytes_used, buffer.len() as _);
					Cow::Owned(String::from_utf8_unchecked(buffer))
				}
			}
		}
	}
	impl fmt::Display for CFString {
		fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
			fmt.write_str(&Cow::from(self))
		}
	}
	impl fmt::Debug for CFString {
		fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
			write!(f, "\"{}\"", self)
		}
	}
}

type CFURLRef = CFValueRef;
#[derive(Clone)]
pub struct CFURL(pub CFValue);
impl Deref for CFURL {
	type Target = CFValue;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl From<CFURL> for CFValue {
	fn from(value: CFURL) -> Self {
		value.0
	}
}
#[cfg(target_vendor = "apple")]
mod cfurl {

	use super::*;
	impl CFURL {
		pub fn to_path(&self) -> Option<std::path::PathBuf> {
			unsafe {
				let mut buf = [0u8; 1024 as usize];
				let result = CFURLGetFileSystemRepresentation(self.as_ref(), true as Boolean, buf.as_mut_ptr(), buf.len() as CFIndex);
				if result == false as Boolean {
					return None;
				}
				use std::ffi::CStr;
				let len = CStr::from_ptr(buf.as_ptr() as _).count_bytes();
				use std::ffi::OsStr;
				use std::os::unix::ffi::OsStrExt as _;
				let path = OsStr::from_bytes(&buf[0..len]);
				Some(path.into())
			}
		}
	}
}
#[cfg(not(target_vendor = "apple"))]
mod cfurl {
	use super::*;
	impl CFURL {
		pub fn to_path(&self) -> Option<std::path::PathBuf> {
			None
		}
	}
}

pub type CFDictionaryRef = CFValueRef;
#[derive(Clone)]
pub struct CFDictionary(pub CFValue);
impl Deref for CFDictionary {
	type Target = CFValue;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl From<CFDictionary> for CFValue {
	fn from(value: CFDictionary) -> Self {
		value.0
	}
}
impl CFDictionary {
	pub fn from_pairs(pairs: &[(CFValueRef, CFValue)]) -> CFDictionary {
		let (keys, values): (Vec<CFRef>, Vec<CFRef>) = pairs.iter().map(|(key, value)| (*key as CFRef, value.as_cf())).unzip();
		unsafe {
			let dictionary_ref = CFDictionaryCreate(
				kCFAllocatorDefault,
				keys.as_ptr(),
				values.as_ptr(),
				keys.len() as _,
				&kCFTypeDictionaryKeyCallBacks,
				&kCFTypeDictionaryValueCallBacks,
			);
			Self(CFValue::create_rule(dictionary_ref))
		}
	}
}

pub type CFNumberType = u32;
// members of enum CFNumberType
pub const kCFNumberSInt8Type: CFNumberType = 1;
pub const kCFNumberSInt16Type: CFNumberType = 2;
pub const kCFNumberSInt32Type: CFNumberType = 3;
pub const kCFNumberSInt64Type: CFNumberType = 4;
pub const kCFNumberFloat32Type: CFNumberType = 5;
pub const kCFNumberFloat64Type: CFNumberType = 6;
pub const kCFNumberCharType: CFNumberType = 7;
pub const kCFNumberShortType: CFNumberType = 8;
pub const kCFNumberIntType: CFNumberType = 9;
pub const kCFNumberLongType: CFNumberType = 10;
pub const kCFNumberLongLongType: CFNumberType = 11;
pub const kCFNumberFloatType: CFNumberType = 12;
pub const kCFNumberDoubleType: CFNumberType = 13;
pub const kCFNumberCFIndexType: CFNumberType = 14;
pub const kCFNumberNSIntegerType: CFNumberType = 15;
pub const kCFNumberCGFloatType: CFNumberType = 16;
pub const kCFNumberMaxType: CFNumberType = 16;

pub type CFStringEncoding = UInt32;
pub const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;

pub type CFDictionaryApplierFunction = extern "C" fn(key: *const c_void, value: *const c_void, context: *mut c_void);
pub type CFDictionaryRetainCallBack = extern "C" fn(allocator: CFAllocatorRef, value: *const c_void) -> *const c_void;
pub type CFDictionaryReleaseCallBack = extern "C" fn(allocator: CFAllocatorRef, value: *const c_void);
pub type CFDictionaryCopyDescriptionCallBack = extern "C" fn(value: *const c_void) -> CFStringRef;
pub type CFDictionaryEqualCallBack = extern "C" fn(value1: *const c_void, value2: *const c_void) -> Boolean;
pub type CFDictionaryHashCallBack = extern "C" fn(value: *const c_void) -> CFHashCode;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CFDictionaryKeyCallBacks {
	pub version: CFIndex,
	pub retain: CFDictionaryRetainCallBack,
	pub release: CFDictionaryReleaseCallBack,
	pub copyDescription: CFDictionaryCopyDescriptionCallBack,
	pub equal: CFDictionaryEqualCallBack,
	pub hash: CFDictionaryHashCallBack,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct CFDictionaryValueCallBacks {
	pub version: CFIndex,
	pub retain: CFDictionaryRetainCallBack,
	pub release: CFDictionaryReleaseCallBack,
	pub copyDescription: CFDictionaryCopyDescriptionCallBack,
	pub equal: CFDictionaryEqualCallBack,
}

#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
	pub fn CFRelease(cf: CFRef);
	pub fn CFRetain(cf: CFRef) -> CFRef;

	pub static kCFAllocatorDefault: CFAllocatorRef;
	pub static kCFAllocatorSystemDefault: CFAllocatorRef;
	pub static kCFAllocatorMalloc: CFAllocatorRef;
	pub static kCFAllocatorMallocZone: CFAllocatorRef;
	pub static kCFAllocatorNull: CFAllocatorRef;
	pub static kCFAllocatorUseContext: CFAllocatorRef;
	/* CFNumber */
	pub fn CFNumberCreate(allocator: CFAllocatorRef, theType: CFNumberType, valuePtr: *const c_void) -> CFNumberRef;
	/* CFString */
	pub fn CFStringCreateWithBytes(
		alloc: CFAllocatorRef,
		bytes: *const UInt8,
		numBytes: CFIndex,
		encoding: CFStringEncoding,
		isExternalRepresentation: Boolean,
	) -> CFStringRef;
	pub fn CFStringCreateWithBytesNoCopy(
		alloc: CFAllocatorRef,
		bytes: *const UInt8,
		numBytes: CFIndex,
		encoding: CFStringEncoding,
		isExternalRepresentation: Boolean,
		contentsDeallocator: CFAllocatorRef,
	) -> CFStringRef;
	pub fn CFStringGetBytes(
		theString: CFStringRef,
		range: CFRange,
		encoding: CFStringEncoding,
		lossByte: UInt8,
		isExternalRepresentation: Boolean,
		buffer: *mut UInt8,
		maxBufLen: CFIndex,
		usedBufLen: *mut CFIndex,
	) -> CFIndex;
	pub fn CFStringGetCStringPtr(theString: CFStringRef, encoding: CFStringEncoding) -> *const c_char;
	pub fn CFStringGetLength(theString: CFStringRef) -> CFIndex;
	/* CFURL */
	pub fn CFURLGetFileSystemRepresentation(anURL: CFURLRef, resolveAgainstBase: Boolean, buffer: *mut u8, maxBufLen: CFIndex) -> Boolean;
	pub fn CFURLGetString(anURL: CFURLRef) -> CFStringRef;
	/* CFDictionary */
	pub static kCFTypeDictionaryKeyCallBacks: CFDictionaryKeyCallBacks;
	pub static kCFCopyStringDictionaryKeyCallBacks: CFDictionaryKeyCallBacks;
	pub static kCFTypeDictionaryValueCallBacks: CFDictionaryValueCallBacks;
	pub fn CFDictionaryCreate(
		allocator: CFAllocatorRef,
		keys: *const *const c_void,
		values: *const *const c_void,
		numValues: CFIndex,
		keyCallBacks: *const CFDictionaryKeyCallBacks,
		valueCallBacks: *const CFDictionaryValueCallBacks,
	) -> CFDictionaryRef;
	/* CFArray */
	pub fn CFArrayGetCount(theArray: CFArrayRef) -> CFIndex;
	pub fn CFArrayGetValueAtIndex(theArray: CFArrayRef, idx: CFIndex) -> *const c_void;
}
